import gobject
import threading
import time

from util.decorators import *
import chunk, message

from util import config

import logging
logger = logging.getLogger('async.receiver')
class Receiver(gobject.GObject):

    __gsignals__ = {
        'message-complete' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                (gobject.TYPE_PYOBJECT,)),
        'need-retransmission' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT,)),
        'busy' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                (gobject.TYPE_BOOLEAN,)),
    }

    lock = threading.Lock()

    def __init__(self):
        gobject.GObject.__init__(self)
        self.__incoming = {}
        self.__timers = {}
        self.__busy = False
        self.__recent_completed = []

    def shutdown(self):
        return self.__incoming

    @synchronized(lock)
    def receive(self, data, addr):
        if not self.__busy:
            self.__busy = True
            self.emit('busy', True)
        c = chunk.Chunk.from_raw(data)
        logger.debug('Received mid %i, seqno %i from %s', c.mid, c.seq, addr)
        self.__stop_timer(c.mid)
        if c.mid in self.__recent_completed:
            logger.info('Chunk was for a recently completed message, id %i. Ignoring.', c.mid)
            return # The message was recently completed; packets must have come in out of order.
        msg = self.__incoming[c.mid]
        if isinstance(c, chunk.EndChunk):
            self.__check_complete(c.mid)
        else:
            msg.put_chunk(c)
            self.__start_timer(c.mid)
            
    @synchronized(lock)
    def resume_incomplete(self, msg):
        assert msg.header.mid not in self.__incoming
        self.__incoming[msg.header.mid] = msg
        self.__start_timer(msg.header.mid)

    @synchronized(lock)
    def register_incoming(self, header):
        assert header.mid not in self.__incoming
        self.__incoming[header.mid] = message.create_from_header(header)

    def __stop_timer(self, mid):
        if mid in self.__timers:
            self.__timers[mid].cancel()
            del self.__timers[mid]

    def __start_timer(self, mid):
        if mid in self.__timers:
            self.__stop_timer(mid)
        t = threading.Timer(config.get_int('async', 'completion-check-timeout-s'), self.__synced_check_complete, [mid])
        self.__timers[mid] = t
        t.start()

    @synchronized(lock)
    def __synced_check_complete(self, mid):
        self.__check_complete(mid)

    def __check_complete(self, mid):
        self.__busy = False
        self.emit('busy', False)

        msg = self.__incoming[mid]
        incomplete = msg.get_incomplete_seqs()
        if incomplete:
            logger.debug('Message %i incomplete; seqs: %s', msg.header.mid, incomplete)
            self.emit('need-retransmission', msg.header, incomplete)
            self.__start_timer(mid)
        else:
            del self.__incoming[mid]
            self.emit('message-complete', msg)
            self.__recent_completed.append(mid)

gobject.type_register(Receiver)
